#ifndef _OPTIONS_H_
#define _OPTIONS_H_

#include <iostream>
#include <string>
#include <string.h>
#include <list>
#include "except.h"

typedef std::list<std::string> strlist;

class Args : public strlist
{
public:
  Args(int argc, char *argv[])
    : strlist(), name(argv[0])
  {
    for (int i=1; i<argc ; i++)
      {
	push_back(argv[i]);
      }
  }
  void dump()
  {
    Args::iterator i;
    std::cout << name << std::endl;
    for(i=begin();i!=end(); i++)
      {
	std::cout << *i << std::endl;
      }
  }
  std::string name;
};

class Opt
{
public:
  Opt(std::string optstring)
    : __name(optstring), __present(false)
  {}

  virtual std::string get_name()
  {
    return __name;
  }

  virtual void usage()
  {
    std::cerr << __name;
  }

  virtual void parse(Args& args)
  {
    Args::iterator i;
    Args::iterator j;

    for (i=args.begin();i!=args.end();i++)
      {	
	if(!get_name().compare(*i) )
	  {
	    set_present();
	    j=i;
	  }
      }
    if(is_present())
      args.erase(j);
  }
  virtual void chk_dup()
  {
    if(is_present())
      {
	std::cerr << "Only 1 " << get_name() << " Allowed" << std::endl;
	CommandLineArgumentError exc; 
	throw exc;
      }
  }
  
  virtual bool is_present()
  {
    return __present;
  }

  virtual void set_present(bool val=true )
  {
    chk_dup();
    __present=val;
  }
  
  virtual ~Opt()
  {}
  
  virtual void help() =0;
  
protected:
  virtual void indent_1st()
  {
    std::cerr << "         " ;
  }
  virtual void indent_2nd()
  {
    indent_1st();
    std::cerr << "         " ;
  }
private:
  std::string __name;  
  bool __present;
};


class OptValue : public Opt
{
public:
  OptValue(std::string optstring, std::string valname)
    :  Opt(optstring), __valname(valname), __val()
  {}
  
  virtual void usage()
  {
    std::cerr << get_name() << get_val_name();
  }

  virtual std::string get_val_name()
  {
    std::string str(" <");
    str += __valname; 
    str += ">";
    return str;
  }
  
  virtual void set_value(std::string  value)
  { __val=value; }

  virtual void parse(Args& args)
  {
    Args::iterator i;
    
    Args::iterator j1;
    Args::iterator j2;
    for (i=args.begin();i!=args.end();i++)
      {	
	if(!get_name().compare(*i) )
	  {
	    set_present();
	    j1=i;
	    j2=i;
	    j2++;
	    set_value(*j2);
	  }
      }
    if(is_present())
      {
	args.erase(j1);
	args.erase(j2);
      }
  }
  
  virtual std::string get_value()
  {
    if(!is_present())
      {
	std::cerr << "No " << get_name() << " option but "
		  << get_val_name()      << " requested" 
		  << std::endl; 
	CommandLineArgumentError exc;
	throw exc;	
      }
    return __val; 
  }
  
  virtual void help() =0;
  
private:
  std::string __valname;
  std::string __val;
};

class networkopt : public OptValue
{
public:
  networkopt() : OptValue("network", "net name")
  {}
  virtual void help()
  {
    indent_1st();
    usage();
    std::cerr << std::endl;
    indent_2nd();
    std::cerr << "only connect to network: " << get_val_name()
	      << std::endl;
  }
};

class noconnectopt : public Opt
{
public:
  noconnectopt() : Opt("noconnect")
  {}
  virtual void help()
  {
    indent_1st();
    usage();
    std::cerr << std::endl;
    indent_2nd();
    std::cerr << "do not make changes - will chow changes to stdout"
	      << std::endl;
  }
};

class deviceopt : public OptValue
{
public:
  deviceopt() : OptValue("device","adapter")
  {}
  virtual void help()
  { 
    indent_1st();
    usage();
    std::cerr << std::endl;
    indent_2nd();
    std::cerr << "only use device: " << get_val_name()
	      << std::endl;
  }
};


class static_ipopt : public OptValue
{
public:
  static_ipopt() : OptValue("static_ip","ip_address")
  {}
  virtual void help()
  {
    indent_1st();
    usage();
    std::cerr << std::endl;
    indent_2nd();
    std::cerr << "use static ip: " << get_val_name()
	      << std::endl;
  }
};

class gatewayopt : public OptValue
{
public:
  gatewayopt() : OptValue("gateway","gateway_ip")
  {}
  virtual void help()
  {
    indent_1st();
    usage();
    std::cerr << std::endl;
    indent_2nd();
    std::cerr << "use network gateway: " << get_val_name()
	      << std::endl;
  }
};

class dhcpopt : public Opt
{
public:
  dhcpopt() : Opt("dhcp")
  {}
  virtual void help()
  {
    indent_1st();
    usage();
    std::cerr << std::endl;
    indent_2nd();
    std::cerr << "use dhcp protocol (default)"
	      << std::endl;
  }
};


class bootpopt : public Opt
{
public:
  bootpopt() : Opt("bootp")
  {}
  virtual void help()
  {
    indent_1st();
    usage();
    std::cerr << std::endl;
    indent_2nd();
    std::cerr << "use bootp protocol"
	      << std::endl;
  }
};

class OptHelp : public Opt
{
public:
  OptHelp(std::string optstring)
    :  Opt(optstring)
  {}
  
  virtual void usage()
  {
    std::cerr << get_name();
  }
  
  virtual void help()
  {
    indent_1st();
    usage();
    std::cerr << std::endl;
    indent_2nd();
    std::cerr << "This message"
	      << std::endl;
  }
  virtual void parse(Args& args)
  {
    Args::iterator i;
    
    for (i=args.begin();i!=args.end();i++)
      {	
	if(!get_name().compare(*i) )
	  {
	    set_present();
	  }
      }
  }
  
  virtual ~OptHelp()
  {}
};

class Options
{
public:
  Options(int argc, char *argv[])
    : __args(argc,argv), __network(),   __noconnect(),  __help("help"),
      __device(),        __static_ip(), __gateway(),    __dhcp(),  
      __bootp()
  {      
    
    // __args.dump();
    // parse help first since if help is present we're done.
    __help.parse(__args);
    if(__help.is_present())
       help();
    // then parse commands with values since values might conflict
    __network.parse(__args);
    __device.parse(__args);
    __static_ip.parse(__args);
    __gateway.parse(__args);
    // then parse the rest
    __noconnect.parse(__args);
    __dhcp.parse(__args);
    __bootp.parse(__args);

    // check for remaining garbage
    Args::iterator i;
    bool crap=false;      
    for (i=__args.begin();i!=__args.end();i++)
      {	
	crap=true;
	std::cerr << "unknown argument: " << *i << std::endl;
      }
    if(crap)
      {
	usage();
	CommandLineArgumentError exc; 
	throw exc;
      }

    // check to make sure only one of dhcp | bootp | static_ip are there
    bool ambiguous=false;
    if(has_dhcp() && (has_bootp() || has_static_ip()) )
      ambiguous=true;    
    else if(has_bootp() && has_static_ip())
      ambiguous=true;
    
    if(ambiguous)
      {
	std::cerr << "Only One of dhcp | bootp | static_ip may be specified" 
		  << std::endl;
	CommandLineArgumentError exc; 
	throw exc;
      }
    // check to make sure gateway not specified without static_ip
    if(has_gateway() && !has_static_ip())
      {
	std::cerr << "Error: gateway specified but no static_ip" 
		  << std::endl;
	CommandLineArgumentError exc; 
	throw exc;
      }
  }

  virtual bool has_dhcp()
  {
    return __dhcp.is_present();
  }
  virtual bool has_bootp()
  {
    return __bootp.is_present();
  }
  virtual bool has_static_ip()
  {
    return __static_ip.is_present();
  }
  virtual bool has_gateway()
  {
    return __gateway.is_present();
  }
  
  virtual bool has_network()
  {
    return __network.is_present();
  }
  
  virtual bool has_noconnect()
  {
    return __noconnect.is_present();
  }
  
  virtual bool has_device()
  {
    return __device.is_present();
  }
  
  virtual std::string network_name()
  {
    return __network.get_value();
  }
  
  virtual std::string device_name()
  {
    return __device.get_value();
  }
  
  virtual std::string ip_address()
  {
    return __static_ip.get_value();
  }
  virtual std::string gateway_ip()
  {
    return __gateway.get_value();
  }
  
  virtual void usage()
  {
    std::cerr << __args.name;
    opt_begin();
    __network.usage();
    opt_sep();
    __noconnect.usage();
    opt_sep();    
    __device.usage();
    opt_end(); 
    std::cerr << std::endl;
    indent_1st();
    opt_begin();    
    __dhcp.usage();
    opt_choice();
    __bootp.usage();
    opt_choice();
    __static_ip.usage();
    opt_sep();
    __gateway.usage();
    opt_end();
    std::cerr << std::endl;
    indent_1st();
    opt_begin();
    __help.usage();
    opt_end();
  }      
  virtual void indent_1st()
  {
    std::cerr << "          " ;
  }

  void help()
  {
    std::cerr 
      << __args.name
      << "--  Scan and connect to wireless networks"
      << std::endl 
      << std::endl
      << "Command Syntax:"
      << std::endl;
    usage();
    std::cerr 
      << std::endl 
      << "Options:"
      << std::endl;
    __network.help();
    __noconnect.help();
    __device.help();
    __dhcp.help();
    __bootp.help();
    __static_ip.help();
    __gateway.help();
    __help.help();
    
    NormalExit exc;
    throw exc;
  }

  virtual ~Options()
  {}
private:
  
  void opt_sep()
  {
    opt_end();
    opt_begin();
  }
  void opt_choice()
  {
    opt_end();
    std::cerr << " |";
    opt_begin();
  }
  
  void opt_end()
  {
    std::cerr << "]" ;
  }
  void opt_begin()
  {
    std::cerr << " [" ;
  }
  
  Args __args;
  networkopt  __network;
  noconnectopt __noconnect;
  OptHelp __help;
  deviceopt __device;
  static_ipopt __static_ip;
  gatewayopt __gateway;
  dhcpopt __dhcp;
  bootpopt __bootp;
};

#endif
